using System;
using System.Collections.Generic;
using System.Diagnostics;
using FineCollection.Intf;
using FineCollection.Utils;



namespace FineCollection.Const
{
    
    /// <summary>
    /// Base class for all constant collections.
    /// </summary>
    /// <stereotype>value object</stereotype>
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(ConstArrayDebugView<>))]
    [Serializable]
    public abstract class ConstArray<T> : RRandomList<T>, ICollection<T>, Constant
    {

        #region STATIC SERVICE MEMBERS

        internal static readonly IEqualityComparer<T> equator =
            EqualityComparer<T>.Default;


        #endregion



        #region STATE

        /// <summary>
        /// Array with items. Immutable.
        /// </summary>
        /// <remarks>
        /// Note: really, the array can be filled partially - in this
        /// case the <see cref="count"/> will be not equal to the size of the array.
        /// But <c>arr.Length</c> will be not less that the <c>count</c>.
        /// </remarks>
        internal readonly T[] arr;

        /// <summary>
        /// Index of the first item.
        /// </summary>
        internal readonly int firstIndex;

        /// <summary>
        /// Index of the last item.
        /// </summary>
        internal readonly int lastIndex;

        /// <summary>
        /// Number of items in the collection.
        /// </summary>
        internal readonly int count;

        /// <summary>
        /// Hash code, or int.MinValue if not calculated yet.
        /// </summary>
        internal int hc = Int32.MinValue;

        #endregion



        #region CONSTRUCTORS

        /// <summary>
        /// This constructor is for internal usage only.
        /// </summary>
        /// <param name="firstIndex">index of the first item.</param>
        /// <param name="array">array with items.</param>
        /// <param name="count">count of real itema in the array.</param>
        /// <param name="takeOwnership">true - use this array as is, 
        ///                             false - copy its content to a new array.</param>
        internal ConstArray(int firstIndex, T[] array, int count, bool takeOwnership)
        {
            this.firstIndex = firstIndex;
            this.lastIndex = firstIndex + (count - 1);
            this.count = count;

            if (takeOwnership)
            {
                this.arr = array;
            }
            else
            {   
                this.arr = new T[count];
                Array.Copy(array, this.arr, count);
            }
        }

        #endregion



        #region RRANDOMLIST MEMBERS

        /// <summary>
        /// Number of items in the collection.
        /// </summary>
        public int Count
        {
            get { return count; }
        }

        /// <summary>
        /// Checks whether the collection is empty.
        /// </summary>
        public bool IsEmpty
        {
            get { return count == 0; }
        }

        /// <summary>
        /// checks whether the collection contains at least one item.
        /// </summary>
        public bool IsNotEmpty
        {
            get { return count > 0; }
        }


        public RIterator<T> TakeIterator()
        {
            return new ArrIterator<T>(this);
        }


        public T First
        {
            get
            {
                if (count > 0)
                    return arr[0];
                else
                    throw new CollectionIsEmptyException();
            }
        }

        public T Last
        {
            get
            {
                if (count > 0)
                    return arr[count-1];
                else
                    throw new CollectionIsEmptyException();
            }
        }

        public int FirstIndex
        {
            get { return firstIndex; }
        }


        public int LastIndex
        {
            get { return lastIndex; }
        }


        public T this[int index]
        {
            get
            {
                if (firstIndex <= index && index <= lastIndex)
                    return arr[index - firstIndex];
                else
                    throw new IndexOutOfBoundsException(index, firstIndex, lastIndex);
            }
        }


        T RCollection<T>.this[Predicate<T> predicate]
        {
            get { return Find(predicate, default(T)); }
        }


        public T Find(Predicate<T> predicate, T defaultValue)
        {
            for (int i = 0; i < count; i++)
                if (predicate(arr[i]))
                    return arr[i];
            return defaultValue;
        }


        #endregion



        #region ICOLLECTION MEMBERS

        void ICollection<T>.Add(T item)
        {
            throw new AttemptToModifyConstantException("Using 'Add(item)' on constant collection.");
        }

        void ICollection<T>.Clear()
        {
            throw new AttemptToModifyConstantException("Using 'Clear()' on constant collection.");
        }

        void ICollection<T>.CopyTo(T[] array, int arrayIndex)
        {
            if (array == null)
                throw new System.ArgumentNullException("array");
        }

        bool ICollection<T>.Remove(T item)
        {
            throw new AttemptToModifyConstantException("Using 'Remove(item)' on constant collection.");
        }

        bool ICollection<T>.IsReadOnly
        {
            get { return true; }
        }

        #endregion



        #region OWN MEMBERS


        /// <summary>
        /// Counts how many times the specified item presents in the collection.
        /// </summary>
        /// <param name="item">item to count.</param>
        /// <returns>count.</returns>
        public abstract int CountOf(T item);
        

        /// <summary>
        /// Counts how many items meet the specified predicate.
        /// </summary>
        /// <param name="predicate">predicate to match.</param>
        /// <returns>number of items.</returns>
        public int CountOf (Func<T,bool> predicate)
        {
            int n = 0;
            for (int i = 0; i < count; i++)
                if ( predicate(arr[i]) )
                    n++;
            return n;
        }
        

        /// <summary>
        /// Looks for the first item, that meets the specified predicate.
        /// </summary>
        /// <param name="predicate">predicate to match.</param>
        /// <returns>first found item, or <tt>default(T)</tt> in no any.</returns>
        public T this[Func<T,bool> predicate]
        {
            get
            {
                for (int i = 0; i < count; i++)
                    if ( predicate(arr[i]) )
                        return arr[i];
                return default(T);
            }
        }


        /// <summary>
        /// Checks that the specified item contains in the collection.
        /// </summary>
        /// <param name="item">items to look for.</param>
        /// <returns>contains.</returns>
        public abstract bool Contains(T item);


        /// <summary>
        /// Checks whether the collection contains at least one item 
        /// that meets the specified predicate.
        /// </summary>
        /// <param name="predicate">predicate to check.</param>
        /// <returns>contains.</returns>
        public bool Contains(Func<T,bool> predicate)
        {
            for (int i = 0; i < count; i++)
                if (predicate(arr[i]))
                    return true;
            return false;
        }


        /// <summary>
        /// Looks for the first occurance of the specified item, 
        /// and returns its index.
        /// </summary>
        /// <param name="item">item to look for.</param>
        /// <returns>index of the found item in 
        ///          the <see cref="FirstIndex"/>..<see cref="LastIndex"/> range;
        ///          or <see cref="int.MinValue"/> if not found.</returns>
        public abstract int IndexOf(T item);


        /// <summary>
        /// Looks for the first item, that meets the specified predicate,
        /// and returns its index.
        /// </summary>
        /// <param name="predicate">predicate to match.</param>
        /// <returns>item's index, or <see cref="int.MinValue"/> if not found.</returns>
        public int IndexOf(Predicate<T> predicate)
        {
            for (int i = 0; i < count; i++)
                if (predicate(arr[i]))
                    return firstIndex + i;
            return Int32.MinValue;
        }


        public int IndexOf(Predicate<T> predicate, int startFromIndex)
        {
            if (!(firstIndex <= startFromIndex && startFromIndex <= lastIndex))
                throw new IndexOutOfBoundsException(startFromIndex, firstIndex, lastIndex);

            int offset = startFromIndex - firstIndex;
            for (int i = offset; i < count; i++)
                if (predicate(arr[i]))
                    return firstIndex + i;
            return int.MinValue;
        }


        /// <summary>
        /// Makes an array (shallow) copy of this collection,
        /// preserving the items order.
        /// </summary>
        /// <returns>array copy.</returns>
        public T[] ToArray()
        {
            T[] result = new T[count];
            Array.Copy(arr, result, count);
            return result;
        }


        /// <summary>
        /// Converts the collection to a constant list.
        /// </summary>
        /// <returns></returns>
        public abstract ConstList<T> ToList();


        /// <summary>
        /// Converts the collection to a constant set.
        /// </summary>
        /// <returns></returns>
        public abstract ConstSet<T> ToSet();



        #endregion



        #region LEGACY ITERATORS

        System.Collections.Generic.IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator()
        {
            return new ArrIterator<T>(this);
        }


        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new ArrIterator<T>(this);
        }

        #endregion



        #region ITERATOR CLASS

        internal class ArrIterator<TT> : RIterator<TT>,
                                         System.Collections.Generic.IEnumerator<TT>
        {
            internal readonly ConstArray<TT> ia;
            internal int index;
            internal bool inBounds;

            internal ArrIterator(ConstArray<TT> ia)
            {
                this.ia = ia;
                this.index = -1;
                inBounds = false;
            }

            internal ArrIterator(ConstArray<TT> ia, int index)
            {
                this.ia = ia;
                this.index = index;
                inBounds = index >= 0 && index < ia.count;
            }

            public void Reset() 
            {
                index = -1;
                inBounds = false;
            }

            public bool Next()
            {
                index++;
                inBounds = index < ia.count;
                return inBounds;
            }

            public TT Current
            {
                get
                {
                    if (inBounds)
                        return ia.arr[index];
                    else
                        throw new IndexOutOfBoundsException(ia.firstIndex + index,
                                                            ia.firstIndex,
                                                            ia.firstIndex + ia.count - 1);
                }
            }


            public sbyte Where
            {
                get
                {
                    if (index < 0)
                        return -1;
                    else if (index < ia.count)
                        return 0;
                    else
                        return +1;
                }
            }


            public RIterator<TT> Clone()
            {
                return new ArrIterator<TT>(ia, index);
            }


            bool System.Collections.IEnumerator.MoveNext()
            {
                if (Where < +1)
                    return Next(); // shifts the iterator
                else
                    return false;
            }

            object System.Collections.IEnumerator.Current
            {
                get { return Current; }
            }

            void System.IDisposable.Dispose()
            {
                // do nothing
            }


            public override string ToString()
            {
                if (index < 0)
                    return "begin";
                else if (index < ia.count)
                    return (ia.firstIndex+index).ToString() + "->" + Current.ToString();
                else return "end";
            }
        }

        #endregion



        #region LEGACY FUNCTIONS


        /// <summary>
        /// Symmetrical hash code. 
        /// Calculates the hash code that depends on contained items
        /// but doesn't depend on their order.
        /// </summary>
        /// <returns>Hash code.</returns>
        public override int GetHashCode()
        {
            if (hc == int.MinValue)
            {
                hc = 0;
                for (int i = 0; i < count; i++)
                    hc = hc * 3 + equator.GetHashCode(arr[i]);
                hc &= 0x7FFFFFFF;
                if (hc == 0 && count > 0)
                    hc = count;
            }

            return hc;
        }


        public bool Equals(ConstArray<T> that)
        {
            if (ReferenceEquals(this, that))
                return true;

            if (this.firstIndex != that.firstIndex)
                return false;

            if (this.count != that.count)
                return false;

            if (this.GetHashCode() != that.GetHashCode())
                return false;

            return CheckEqualityItemByItem(that);
        }


        public override bool Equals(object obj)
        {
            var that = obj as ConstArray<T>;
            return (that != null) && this.Equals(that);
        }


        internal virtual bool CheckEqualityItemByItem(ConstArray<T> that)
        {
            for (int i = 0; i < count; i++)
                if (!equator.Equals(this.arr[i], that.arr[i]))
                    return false;
            return true;
        }


        public override string ToString()
        {
            if (IsEmpty)
                return "[]";

            var buf = new System.Text.StringBuilder(count * 8);
            bool second = false;

            buf.Append('[');

            for (int i = 0; i < count; i++)
            {
                if (second)
                    buf.Append(',');
                else
                    second = true;

                buf.Append(arr[i].ToString());
            }

            buf.Append(']');

            return buf.ToString();
        }

        #endregion



        #region OPERATORS


        public static bool operator == (ConstArray<T> collectionA, ConstArray<T> collectionB)
        {
            return ReferenceEquals(collectionA, collectionB)
                || (!ReferenceEquals(collectionA, null) && !ReferenceEquals(collectionB, null) && collectionA.Equals(collectionB));
        }


        public static bool operator != (ConstArray<T> collectionA, ConstArray<T> collectionB)
        {
            return !(collectionA == collectionB);
        }


        #endregion



    }



    #region DEBUG HELPER

    /// <summary>
    /// Debug helper.
    /// </summary>
    internal class ConstArrayDebugView<T>
    {
        private readonly ConstArray<T> constArray;


        public ConstArrayDebugView(ConstArray<T> constArray)
        {
            this.constArray = constArray;
        }


        [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
        public Array Items
        {
            get
            {
                if (constArray != null && constArray.IsNotEmpty)
                    return DebugViewUtils.MakeArray(constArray.arr,
                                                    constArray.firstIndex,
                                                    constArray.count);
                else
                    return new T[0];
            }
        }
    }

    #endregion
}